<?php

class GameService extends AbstractService
{
    /**
     * @var GameService
     */
    private static $instance;

    /**
     * @var int 语言类型
     */
    private $lang;

    /**
     * @var SzRedisCache
     */
    private $cache;

    /**
     * Get the instance of GameService.
     *
     * @return GameService
     */
    public static function get()
    {
        if (!self::$instance) {
            self::$instance = new GameService();
        }
        return self::$instance;
    }

    /**
     * Construction.
     */
    protected function __construct()
    {
        parent::__construct();
        $this->cache = $this->context->getAppCache();
    }

    /**
     * 玩家创建时，为玩家添加游戏基础数据
     *
     * @param int $shardId
     * @return void
     */
    public function handleCreate($shardId)
    {

    }

    /**
     * 处理玩家登录后，需要计算的逻辑.
     * <pre>
     * 登录逻辑分为两块：离线后登录的更新逻辑，每日首次登录逻辑
     * </pre>
     *
     * @param int $userId
     * @param ModuleProfileVo $profile
     * @param boolean $preHandle
     * @return void
     */
    public function handleLogin($userId, $profile = null, $preHandle = false)
    {
        // FIXME 从 service 的 handleLogin 中处理各自的登录逻辑。
        if (!$profile) {
            $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        }

        // 登录第一步，必须先完成补丁的处理，否则后续的数据结构都是错的。
        if ($preHandle == false) {//只有在game.init才执行的逻辑
            // 清空接口请求误差值
            $this->context->getAppCache($userId)->set(GameConst::CACHE_KEY_TIME_DELTA . $userId, 0, SzTime::HOURS24);
            // 打上数据补丁
            PatchService::get()->handleLogin($userId, $profile);
            // 支付相关同步
            PayService::get()->handleLogin($userId);
            // 种植相关
            PlantService::get()->handleLogin($userId);
            // 记录玩家登录IP
            UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_LAST_IP, SzUtility::getClientHost());
        }
        // 任务登录逻辑，后续的登录逻辑中的需要事先为其创建任务。
        MissionService::get()->handleLogin($userId);
        // 能量恢复
        UserService::get()->handleLogin($userId, $profile);
        // 清空建筑订单
        OrderService::get()->handleLogin($userId);
        // 好友信息更新
        FriendService::get()->handleLogin($userId);
        // 每日登录数据采集
        StatisticsService::get()->handleLogin($userId, $profile);
        // 每日首次登录检查仓库库存
        WarehouseService::get()->handleLogin($userId);
        // 分享首次登陆检查
        ShareService::get()->handleLogin($userId);
        // 打地鼠首次登陆检查
        MonsterService::get()->handleLogin($userId);
        // 刷新视频订单
        OrderService::get()->clearVideoOrder($userId);
        // 刷新活动
        ActivityService::get()->handleLogin($userId);
        // 删除过期邮件
        NewMailService::get()->autoDelMail($userId);
        // 刷新视频buff订单
        OrderService::get()->updateFirstPositionOrder($userId);
        /**
         * 用户登录后，并且在完成上述 service 的登录逻辑之后，进行处理的数据
         */
        // 记录登录时间
        $profile->setLastLoginTime(GameService::get()->getReqTime());
        SzPersister::get()->setVo($profile);
    }

    /**
     * 游戏登录-前端初始化玩家数据
     *
     * @param int $userId
     * @throws SzException 20003
     * @return mixed
     */
    public function info($userId)
    {
        list($profile, $profileInfoList) = UserService::get()->info($userId);
        if (!$profile) {
            throw new SzException(20003, $userId);
        }

        list($profilePayment) = PayService::get()->info($userId);
        list($itemList, $itemLogsList, $buildingItems) = ItemService::get()->info($userId);
        list($warehouseList) = WarehouseService::get()->info($userId);
        list($groundList) = GroundService::get()->info($userId);
        list($plantList) = PlantService::get()->info($userId);
        list($seedList) = SeedService::get()->info($userId);
        list($buildingList) = BuildingService::get()->info($userId);
        list($orderList, $orderBoardList, $orderBoardRewardList) = OrderService::get()->info($userId);
        list($requestList, $selfRequestList, $friendList, $friendCode) = FriendService::get()->info($userId);
        list($goodsList) = GoodsService::get()->info($userId);
        list($missionList) = MissionService::get()->info($userId);
        list($debrisList, $collectionList, $handbookList) = CollectionService::get()->info($userId);
        list($mineList) = MineService::get()->info($userId);
        //list($dropList) = DropService::get()->info($userId);
        list($ikebanaList) = IkebanaService::get()->info($userId);
        //        list($cultivateLandList, $cultivateFlowerList) = CultivateService::get()->info($userId);
        list($shareList) = ShareService::get()->info($userId);
        list($videoList) = VideoService::get()->info($userId);
        list($sevenEventList) = ActivitySevenEventService::get()->info($userId);
        list($dailyLoginList) = ActivityDailyLoginService::get()->info($userId);
        $newCultivateLandList = NewCultivateService::get()->info($userId);

        $cultivateTouchUsersNum = NewCultivateService::get()->getCultivateTouchUsers($userId);
        $compensateOrder = CompensateService::get()->getCompensateOrder($userId);
        $compensateOne = CompensateService::get()->getCompensateOne($userId);
        list($activityList,$eventInviteId) = ActivityService::get()->info($userId);
        $eventInviteStatus = 0;
        if ($eventInviteId) {
            $eventInviteList = SzPersister::get()->getVoList($userId, GameConst::ORM_NAME_EVENT_INVITE);
            if (!$eventInviteList->getElement($eventInviteId,false)) {
                $eventInviteStatus = 1;
            }
        }
        $npcOrderData = NpcOrderService::get()->info($userId);
        if (!$npcOrderData) {
            $npcOrderNewList = $npcOrderOldList = array();
            $npcOrderRefreshTime = strtotime('2099/01/01');
        } else {
            $npcOrderNewList = $npcOrderData['npcOrderNewList'];
            $npcOrderOldList = $npcOrderData['npcOrderOldList'];
            $npcOrderRefreshTime = $npcOrderData['npcOrderRefreshTime'];
        }
        $mailRedPoint = NewMailService::get()->info($userId);

        $friendCount = NewFriendService::get()->info($userId);

        return array(
            $profile,
            $profileInfoList,
            $profilePayment,
            $itemList,
            $itemLogsList,
            $warehouseList,
            $groundList,
            $plantList,
            $seedList,
            $buildingList,
            $orderList,
            $orderBoardList,
            $orderBoardRewardList,
            $requestList,
            $selfRequestList,
            $friendList,
            $friendCode,
            $goodsList,
            $missionList,
            $debrisList,
            $collectionList,
            $handbookList,
            $mineList,
            UserService::get()->getTownName($userId),
            $buildingItems,
            $ikebanaList,
            //            $cultivateLandList,
            //            $cultivateFlowerList,
            $shareList,
            $videoList,
            $newCultivateLandList,
            $cultivateTouchUsersNum,
            $compensateOrder,
            $compensateOne,
            $sevenEventList,
            $npcOrderNewList,
            $npcOrderOldList,
            $npcOrderRefreshTime,
            $activityList,
            $mailRedPoint,
            $friendCount,
            $dailyLoginList,
            $eventInviteStatus,
        );
    }

    /**
     * 游戏登录-前端初始化玩家数据
     *
     * @param int $userId
     * @throws SzException 20003
     * @return array
     */
    public function init($userId)
    {
        /**@var ModuleProfileVo $profile */
        $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        if (!$profile) {
            throw new SzException(20003, $userId);
        }
        // 第二次订单补偿
        CompensateService::get()->compensateOrder2($userId);
        // 玩家登录，数据刷新
        GameService::get()->handleLogin($userId, $profile);

        // 老奶奶建筑物补丁
        NewTaskService::get()->initTaskBuilding($userId);

        // 每日任务
        NewTaskService::get()->initDailyTask($userId);

        // 邀请好友
        NewFriendService::get()->initFriendInvite($userId);

        //扩充地块
        GameService::get()->addNewGroundsAndBuilds($userId);

        //恢复等级
        GameService::get()->recoveryLevel($userId);

        //订单旧数据缓存清理
        $redisDb = SzDbFactory::get()->getDb(null, SzAbstractDb::DB_TYPE_REDIS);
        $npcOrderDatas = $redisDb->hGetAll('NpcOrder:'.$userId);
        foreach ($npcOrderDatas as $key=>$data){
            $redisDb->hDel('NpcOrder:'.$userId,$key);
        }

        // 数据刷新完成，提供数据
        return GameService::info($userId);
    }

    /**
     * 删除玩家栅栏数据
     * @param $userId
     * @throws SzException
     */
    public static function deleteFenceData($userId) {
        //验证删除标记
        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        $profileFlag = $profileInfoList->getElement(GameConst::USER_BUILDING_OR_Ground_DEL,false);
        if (!$profileFlag){
            $gridIdArr = array(
                '20x20','20x22','20x28','30x31','21x19','30x19','32x20','32x23','32x25'
            );//'16x20','16x22','16x28'

            $delGround = false;
            $delBuilding = false;
            $buildIdList = array();
            //老玩家地块进行数据删除
            /** @var GroundVoList $groundList
             *  @var GroundVo $ground
             */
            $groundList = SzPersister::get()->getVoList($userId, GameConst::ORM_NAME_GROUND);
            foreach ($gridIdArr as $key=>$gridId){
                //删除玩家地块数据
                $ground = $groundList->getElement($gridId, false);
                if($ground){
                    //获取玩家建筑层级数据
                    if ($ground->getLayer1Type() != -1){
                        $buildIdList[] = $ground->getLayer1();
                    }
                    if ($ground->getLayer2Type() != -1){
                        $buildIdList[] = $ground->getLayer2();
                    }
                    if ($ground->getLayer3Type() != -1){
                        $buildIdList[] = $ground->getLayer3();
                    }
                    $groundList->deleteElement($gridId);
                    SzPersister::get()->setVoList($groundList);
                    $delGround = true;
                }
                else{
                    continue;
                }
            }

            //老玩家建筑进行数据删除
            /** @var BuildingVoList $buildingList
             *  @var BuildingVo $building
             */
            $buildingList = SzPersister::get()->getVoList($userId, GameConst::ORM_NAME_BUILDING);
            if (!empty($buildIdList)){
                foreach ($buildIdList as $key=>$decorId){
                    $building = $buildingList->getElement($decorId, false);
                    if($building){
                        $buildingList->deleteElement($decorId);
                        SzPersister::get()->setVoList($buildingList);
                        $delBuilding = true;
                    }
                    else{
                        continue;
                    }
                }
            }

            SzPersister::get()->setVoList($groundList);
            SzPersister::get()->setVoList($buildingList);

            if ($delGround || $delBuilding){
                //获得标记
                $profileFlag = new ModuleProfileInfoVo(
                    $userId,
                    GameConst::USER_BUILDING_OR_Ground_DEL,
                    1,
                    time(),
                    true
                );

                $profileInfoList->addElement($profileFlag);
                $profileInfoList->setElement(GameConst::USER_BUILDING_OR_Ground_DEL, $profileFlag);
                SzPersister::get()->setVoList($profileInfoList);
            }
        }

        return;
    }


    public static function recoveryLevel($userId){
        //获取玩家恢复等级标记
        $redisDb = SzDbFactory::get()->getDb(null, SzAbstractDb::DB_TYPE_REDIS);
        $flag = $redisDb->get(GameConst::CACHE_RECOVERY_LEVEL.$userId);
        if ($flag){
            return;
        }

        //获取玩家当前经验值以及等级
        $userLevel = UserService::get()->getUserLevel($userId);
        $userExp = UserService::get()->getUserExp($userId);
        $topLevel = 1;
        //获取等级配置表
        $levelSettings = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELS, null, true);
        foreach ($levelSettings as $confLevel=>$levelConf){
            if ($userExp < $levelConf['exp']){
                continue;
            }
            $topLevel = $confLevel;
        }

        if ($userLevel != $topLevel){
            //恢复玩家等级
            /** @var ModuleProfileVo $profile */
            $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
            $profile->setLevel($topLevel);
            SzPersister::get()->setVo($profile);
        }
        $redisDb->set(GameConst::CACHE_RECOVERY_LEVEL.$userId,1);
        return;
    }

    public static function addNewGroundsAndBuilds($userId){
        $initGroundBlockIds = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME, GameConst::DEFAULT_GAME_SETTING_KEY, true);

        $blockIds = $initGroundBlockIds['initGroundBlockIds'];
        $reqTime = GameService::get()->getReqTime();
        $buildingSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING, null, true);
        $blockObjectSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GROUND_OBJECT, null, true);
        $blockResourceSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GROUND_RESOURCE, null, true);

        /** @var GroundVoList $groundList
         *  @var GroundVo $ground
         */
        $groundList = SzPersister::get()->getVoList($userId, GameConst::ORM_NAME_GROUND);
        $buildingList = SzPersister::get()->getVoList($userId, GameConst::ORM_NAME_BUILDING);

        $gridMap = array();
        foreach ($blockIds as $_blockId) {
            // 未找到地块初始化配置，跳过
            if (!SzUtility::checkArrayKey($_blockId, $blockObjectSettings)) {
                continue;
            }

            // 解锁地块后，自动获得地块上的装饰物
            foreach ($blockObjectSettings[$_blockId]['objectIdArray'] as $_objectId) {
                if (!SzUtility::checkArrayKey($_objectId, $blockResourceSettings)) {
                    continue;
                }

                $buildingDefId = $blockResourceSettings[$_objectId]['itemDefId'];
                $orient = $blockResourceSettings[$_objectId]['direction'];
                $gridId = GroundService::get()->parseCoordinate($blockResourceSettings[$_objectId]['anchorX'], $blockResourceSettings[$_objectId]['anchorY']);

                // 更新gridMapOfDecor
                if (!SzUtility::checkArrayKey($buildingDefId, $gridMap)) {
                    $gridMap[$buildingDefId] = array();
                    $gridMap[$buildingDefId][$orient] = array();
                }

                //过滤玩家已有数据
                $tempGround = $groundList->getElement($gridId,false);
                if (!$tempGround){
                    $gridMap[$buildingDefId][$orient][] = $gridId;
                    // 初始化building
                    $building = BuildingService::get()->create($userId, $buildingDefId, $orient, GameConst::DEFAULT_BOOL_OF_TRUE, GameConst::BUILDING_IS_BUILD, $reqTime);
                    $buildingList->addElement($building);
                }
            }
        }

        // 统一处理装饰物数据, 并手动持久化
        if (count($buildingList->getInsertList()) > 0) {
            // 获取持久化前的 decorId，然后手动持久化
            $originIds = array_keys($buildingList->getList());
            $buildingList->persist();

            // 清除建筑的缓存，并重新获取
            $buildingModel = SzPersister::get()->getModel(GameConst::ORM_NAME_BUILDING);
            SzSystemCache::cache(SzSystemCache::MODEL_VO_LIST_INSTANCE, "{$buildingModel->getVoListClassName()}{$userId}{$userId}");
            $buildingList = SzPersister::get()->getVoList($userId, GameConst::ORM_NAME_BUILDING);

            // 获取持久化后的 decorId，并比对差异，得到刚才新创建的 decorId
            $currentIds = array_keys($buildingList->getList());
            $diffIds = array_diff($currentIds, $originIds);

            // 根据building的自增长id，逐行处理ground数据
            foreach ($diffIds as $_decorId) {
                /** @var BuildingVo $building */
                $building = $buildingList->getElement($_decorId);
                SzPersister::get()->addManuallyInsertedResponse($building, $userId, $_decorId);

                $orient = $building->getOrient();
                $buildingDefId = $building->getBuildingDefId();

                if (!SzUtility::checkArrayKey($buildingDefId, $gridMap) || !SzUtility::checkArrayKey($orient, $gridMap[$buildingDefId]) || !SzUtility::checkArrayKey($buildingDefId,
                        $buildingSettings)
                ) {
                    continue;
                }

                $gridId = array_shift($gridMap[$buildingDefId][$orient]);

                // 更新地块信息
                $ground = $groundList->getElement($gridId, false);
                GroundService::get()->updateGroundWithGridId($userId, $gridId, $buildingSettings[$buildingDefId]['layer'], $_decorId, $ground, $groundList, GameConst::GROUND_LAYER_TYPE_DECORATION);

                // 特殊建筑
                switch ($buildingSettings[$buildingDefId]['type']) {
                    case GameConst::ITEM_CATEGORY_WAREHOUSE:
                        WarehouseService::get()->init($userId, $buildingDefId, $_decorId);
                        break;
                }
            }

            SzPersister::get()->setVoList($groundList);
        }
    }

    /**
     * Create player game data.
     *
     * @param int $userId
     * @param string $platformId
     * @param string $platform
     * @param array $platformInfo
     * @return array
     * <pre>
     * array(ModuleProfileVo, ModuleProfileInfoList)
     * </pre>
     */
    public function createPlayerGameData($userId, $platformId, $platform, $platformInfo)
    {
        // no user profile data, means no user game data, create it
        /**
         * @var ModuleProfileVo $profile
         * @var ModuleProfileInfoVoList $profileInfoList
         */
        list($profile, $profileInfoList) = UserService::get()->init($userId, $platformId, $platform);

        UserService::get()->updatePlatformInfo($userId, $platformInfo);
        // 初始化支付
        PayService::get()->handleCreate($userId);

        // 初始化补丁
        PatchService::get()->handleCreate($userId, $profileInfoList);

        /**
         * @var GroundVoList $groundList
         * @var BuildingVoList $buildingList
         * @var ModuleItemVoList $itemList
         */
        $groundList = $this->createEmptyVoListViaOrm(GameConst::ORM_NAME_GROUND);
        $buildingList = $this->createEmptyVoListViaOrm(GameConst::ORM_NAME_BUILDING);

        // 初始化建筑
        BuildingService::get()->handleCreate($userId, $groundList, $buildingList);

        /**
         * @var ModuleItemVoList $itemList
         */
        $itemList = $this->createEmptyVoListViaOrm(ModuleItemConst::ORM_NAME_ITEM);

        // 初始化背包数据
        ItemService::get()->handleCreate($userId, $itemList);

        // 初始化订单板数据
        OrderService::get()->handleCreate($userId);

        // 初始化游戏币和砖石
        $initItemSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME, GameConst::DEFAULT_GAME_SETTING_KEY, true);
        foreach ($initItemSetting['initMoney'] as $entityId => $count) {
            GameService::get()->handleEntity($userId, $entityId, $count, true);
        }

        // 初始化种子解锁
        //        SeedService::get()->handleCreate($userId);

        // 初始化植物
        //        PlantService::get()->handleCreate($userId, $groundList);

        // 初始化变色/培育地块
        NewCultivateService::get()->initLand($userId);

        return array($profile, $profileInfoList);
    }

    /**
     * Validate is the $delta value (client request happened time - client multi requests sent time)
     * in the valid range or not.
     *
     * 多包API传到服务器的时候，其到达时间和业务逻辑时间有差距，因为有些API打包是迟于其发生时间点的，
     * 因此我们需要客户端上传一个业务逻辑发生的时间点，这个函数则是用来验证其时间是否在允许范围内
     *
     * @param int $userId
     * @param int $reqTime
     * @throws SzException 20008
     * @return void
     */
    public function validateMultiRequestTimeDelta($userId, $reqTime)
    {
        $isOpen = SzConfig::get()->loadAppConfig('app', 'IS_VERIFY_DELTA');

        $sysTime = $this->getSysTime();
        $reqTime = $this->getReqTime($reqTime);

        // 每次的系统服务时间与请求时间的对比，需要加上每次接口请求中的误差值，此误差值在GameInit的时候会被清空成0
        $prevTimeDelta = SzContextFactory::get()->getAppCache($userId)->get(GameConst::CACHE_KEY_TIME_DELTA . $userId);
        $currTimeDeltaFix = $sysTime - $reqTime - $prevTimeDelta;

        // 单包接口的堵塞，引起后一个正常接口返回超时，此处需要将误差值修正
        if (abs($currTimeDeltaFix) > GameConst::MULTI_REQ_TIME_DELTA) {
            // 第一次容错失效，检查实际的接口误差
            $currTimeDelta = $sysTime - $reqTime;
            if ($isOpen && abs($currTimeDelta) > GameConst::MULTI_REQ_TIME_DELTA) {
                throw new SzException(20011, array(SzTime::setTime($sysTime), SzTime::setTime($reqTime), $currTimeDelta, $prevTimeDelta));
            }
        }

        // 本次接口实际误差为下一次的容错值
        $nextTimeDelta = $sysTime - $reqTime;
        SzContextFactory::get()->getAppCache($userId)->set(GameConst::CACHE_KEY_TIME_DELTA . $userId, $nextTimeDelta, SzTime::HOURS24);
    }

    /**
     * Handle entity logic.
     *
     * <b>NOTE:</b>
     * When you user this funciton to handle eneity logics,
     * and give no additional params, that means this funciton
     * will retrieve necessary record from SzPersister,
     * and if you did not store your modified record back into SzPersister,
     * there would be some dirty data in your logic.
     *
     * <pre>
     * The supported entity module are limited:
     *     GameConst::ENTITY_TYPE_TOKEN
     *     GameConst::ENTITY_TYPE_MONEY
     *     GameConst::ENTITY_TYPE_ITEM
     *
     * Obligatory params: $userId, $entityId, $count
     *
     * Type of the additional params are according to the entityId type:
     *     GameConst::ENTITY_TYPE_TOKEN: ModuleProfileVo
     *     GameConst::ENTITY_TYPE_MONEY: ModulePaymentVo
     *     GameConst::ENTITY_TYPE_ITEM: ModuleItemVoList
     *
     * And the return value are also different:
     *     GameConst::ENTITY_TYPE_TOKEN: ModuleProfileVo
     *     GameConst::ENTITY_TYPE_MONEY: ModulePaymentVo
     *     GameConst::ENTITY_TYPE_ITEM: ModuleItemVoList
     * </pre>
     *
     * @param int $userId
     * @param int $entityId
     * @param int $count
     * @param boolean $persist whether to do persistence after entity handling
     * @param boolean $allowExceedLimit default false  when add items whether allow exceed the item limit
     * @param array $effectBuffSettings default array()
     * @param boolean $logCommodityConsume default true
     * @param int $commodityValue default 0
     * @param boolean $isNeedLevel default true
     * @param int $activityId default 0
     * @throws SzException 20051, 20052
     * @return boolean|array
     */
    public function handleEntity(
        $userId,
        $entityId,
        $count,
        $persist,
        $allowExceedLimit = false,
        $effectBuffSettings = array(),
        $logCommodityConsume = true,
        $commodityValue = 0,
        $isNeedLevel = true,
        $activityId = 0
    ) {
        /**
         * @var ModuleProfileInfoVo $profileInfo
         */
        if ($entityId == -1 && $count == -1) {
            return false;
        }
        $res = array();
        $args = func_get_args();
        if (count($args) > 9) {
            // has additional attributes
            $args = array_slice($args, 9);
        } else {
            // does not have additional attributes
            $args = array(null);
        }

        $profile = $args[0];

        list($action, $module, $subId) = EntityService::get()->parseEntityId($entityId);

        if ($action == GameConst::ENTITY_ACTION_CONSUME || $action == GameConst::ENTITY_ACTION_REDUCE) {
            // convert count to negative number according to action
            $count = -abs($count);
        } else if ($action == GameConst::ENTITY_ACTION_GET) {
            if ($count < 0) {
                $count = 0;
            }
            $count = abs($count);
            if (is_array($effectBuffSettings) && !empty($effectBuffSettings)) {
                foreach ($effectBuffSettings as $buffSetting) {
                    $count = ceil($count * (1 + $buffSetting['effectValue']));
                }
            }
        }

        //跟新店铺经营数据
        if ($action==GameConst::ENTITY_ACTION_GET || $action==GameConst::ENTITY_ACTION_CONSUME || $action==GameConst::ENTITY_ACTION_REDUCE) {
            UserShopService::get()->record($userId,2,$subId,$count);
        }

        // result of the entity handling
        $result = null;
        $commodityAmountValue = 0;
        $commodityData = array();
        switch ($action) {
            case GameConst::ENTITY_ACTION_GET:
            case GameConst::ENTITY_ACTION_CONSUME:
            case GameConst::ENTITY_ACTION_REDUCE:
                switch ($module) {
                    case GameConst::ENTITY_TYPE_TOKEN:
                        list($result, $commodityAmountValue, $commodityData) = UserService::get()->updateToken($userId, $count, $profile, false, $logCommodityConsume, $commodityValue);
                        // 处理任务计数
                        if ($count > 0) {
                            MissionService::get()->progress($userId, GameConst::ENTITY_ACTION_GAIN_RESOURCE, GameConst::ENTITY_TYPE_TOKEN, $subId, $count);
                        }
                        break;
                    case GameConst::ENTITY_TYPE_MONEY:
                        //向上取整
                        $count = ceil($count);
                        list($result, $commodityAmountValue, $commodityData) = PayService::get()->updateMoney($userId, $count, $profile, $logCommodityConsume, $commodityValue);
                        break;
                    case GameConst::ENTITY_TYPE_ITEM:
                        list($result, $commodityAmountValue, $commodityData) = ItemService::get()->processItems($userId, array($subId => $count), $profile, $allowExceedLimit, $commodityValue,
                            $isNeedLevel);
                        //普通中的特殊道具处理(不要吐槽，这是需求所致，我也认无奈)
                        $smallType = substr($subId , 0 , 2);
                        switch ($smallType) {
                            case GameConst::ITEM_SPECIAL_SMALL_TYPE['Head_frame']:
                                if ($count > 0){
                                    MidAutumnService::get()->getHeadFrame($userId,$smallType,$subId);
                                }
                                break;
                            default:
                                break;
                        }
                        break;
                    case GameConst::ENTITY_TYPE_EXP:
                        $result = UserService::get()->updateExp($userId, $count, $profile);
                        break;
                    case GameConst::ENTITY_TYPE_DEBRIS:
                        $result = CollectionService::get()->processDebris($userId, $subId, $count);
                        break;
                    case GameConst::ENTITY_TYPE_SHOP_EXP:
                        $result = TransactionService::get()->addExp($userId, $count);
                        break;
                    case GameConst::ENTITY_TYPE_USER_INFO:
                        $infoType = null;
                        switch ($subId) {
                            case GameConst::ITEM_ID_WATER:
                                $infoType = GameConst::USER_INFO_TYPE_WATER;
                                break;
                            case GameConst::ITEM_ID_TURNTABLE_COIN:
                                $infoType = GameConst::USER_INFO_TYPE_TURNTABLE_COIN;
                                break;
                            default:
                                throw new SzException(20052, array($subId, $entityId)); // unsupported user info id
                                break;
                        }
                        // FIXME 这里资源的处理都是允许溢出的（即：使用资源礼包获得的资源）
                        $result = UserService::get()->updateProfileInfo($userId, $infoType, $count, $profile);
                        break;
                    case GameConst::ENTITY_TYPE_GIFT_PACK:
                        $giftPackSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GIFT_PACK, $subId, true);
                        if (!$giftPackSetting) {
                            throw new SzException(20052, array($subId, $entityId)); // unsupported user in
                        }

                        $itemNum = min($giftPackSetting['item_num'], count($giftPackSetting['items']));
                        $itemTotalNum = $giftPackSetting['total'];
                        $total = 0;
                        for ($i = 1; $i <= $itemNum; $i++) {
                            $itemId = SzUtility::getRandomElementByProbability($giftPackSetting['items']);
                            unset($giftPackSetting['items'][$itemId]);
                            if ($i < $itemNum) {
                                $rand = mt_rand(1, ($itemTotalNum - ($itemNum - $i) - $total));
                                $res[$itemId] = $rand;
                                $total += $rand;
                            } else if ($itemTotalNum > $total) {
                                $res[$itemId] = $itemTotalNum - $total;
                            }
                        }
                        list($result, $commodityAmountValue, $commodityData) = ItemService::get()->processItems($userId, $res, $profile, $allowExceedLimit, $commodityValue,
                            $isNeedLevel);
                        break;
                    case GameConst::ENTITY_TYPE_MONTH_CARD:
                        // 月卡
                        $serverTime = $this->getReqTime();
                        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
                        $profileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_MONTH_CARD, false);
                        if (!$profileInfo) {
                            $profileInfo = new ModuleProfileInfoVo(
                                $userId,
                                GameConst::USER_INFO_TYPE_MONTH_CARD,
                                $serverTime,
                                $serverTime,
                                true
                            );
                            $profileInfoList->addElement($profileInfo);
                            $profileInfoList->setElement(GameConst::USER_INFO_TYPE_MONTH_CARD, $profileInfo);
                        }

                        $itemSetting = SzConfig::get()->loadAppConfig(ModuleItemConst::CONFIG_ITEM_DEFS, $subId, true);
                        if (!$itemSetting) {
                            throw new SzException(80001,ModuleItemConst::CONFIG_ITEM_DEFS.'-'.$subId);
                        }
                        $expireTime = (int)$profileInfo->getRawInfo();
                        if ($expireTime >= $serverTime) {
                            $reqTime = $expireTime + abs($count) * $itemSetting['indate'] + 60;
                        } else {
                            $reqTime = $serverTime + abs($count) * $itemSetting['indate'] + 60;
                        }

                        $profileInfo->setInfo($reqTime);
                        $profileInfo->setTime($serverTime);
                        if ($_SERVER['is_payment_callback'] ?? false) $profileInfo->persist();
                        $profileInfoList->updateElement(GameConst::USER_INFO_TYPE_MONTH_CARD, $profileInfo);
                        SzPersister::get()->setVoList($profileInfoList);
                        break;
                    //20190529
                    case GameConst::ENTITY_TYPE_SKIP_VIDEO_CARD:
                        $serverTime = $this->getReqTime();
                        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
                        $profileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_SKIP_VIDEO_CARD, false);
                        if (!$profileInfo) {
                            $profileInfo = new ModuleProfileInfoVo(
                                $userId,
                                GameConst::USER_INFO_TYPE_SKIP_VIDEO_CARD,
                                $serverTime,
                                $serverTime,
                                true
                            );
                            $profileInfoList->addElement($profileInfo);
                            $profileInfoList->setElement(GameConst::USER_INFO_TYPE_SKIP_VIDEO_CARD, $profileInfo);
                        }
                        //获取道具配置
                        $itemSetting = SzConfig::get()->loadAppConfig(ModuleItemConst::CONFIG_ITEM_DEFS, $subId, true);
                        if (!$itemSetting) {
                            throw new SzException(80001,ModuleItemConst::CONFIG_ITEM_DEFS.'-'.$subId);
                        }
                        $expireTime = (int)$profileInfo->getRawInfo();
                        if ($expireTime >= $serverTime) {
                            $reqTime = $expireTime + abs($count) * $itemSetting['indate'] + 60;
                        } else {
                            $reqTime = $serverTime + abs($count) * $itemSetting['indate'] + 60;
                        }

                        $profileInfo->setInfo((int)$reqTime);
                        $profileInfo->setTime($serverTime);
                        if ($_SERVER['is_payment_callback'] ?? false) $profileInfo->persist();
                        if ($_SERVER['is_payment_callback'] ?? false) $profileInfoList->persist();
                        $profileInfoList->updateElement(GameConst::USER_INFO_TYPE_SKIP_VIDEO_CARD, $profileInfo);
                        SzPersister::get()->setVoList($profileInfoList);
                        break;
                    default:
                        throw new SzException(20051, array($entityId, $module)); // unsupported entity module
                }
                break;
            case GameConst::ENTITY_ACTION_BUILDING_RENDERING:
                // 无执行行为
                break;
            //解锁种子
            case GameConst::ENTITY_ACTION_UNLOCK_SEED:
                SeedService::get()->unlockSeed($userId, $subId);
                break;
            default:
                throw new SzException(20052, array($entityId, $action)); // unsupported entity module
        }

        if ($result && $persist) {
            if ($result instanceof SzAbstractVo) {
                SzPersister::get()->setVo($result);
            } else if ($result instanceof SzAbstractVoList) {
                SzPersister::get()->setVoList($result);
            }
        }

        if ($res) {
            $result = $res;
        }

        return array($result, $commodityAmountValue, $commodityData);
    }

    /**
     * Handle entity logic via subId.
     *
     * @param $userId
     * @param $action
     * @param $module
     * @param $subId
     * @param $count
     * @param $persist
     * @param bool $allowExceedLimit
     * @param array $effectBuffSettings
     * @param bool $logCommodityConsume
     * @param int $commodityValue
     * @return array|bool|SzAbstractVo|SzAbstractVoList
     */
    public function handleEntityViaBuild(
        $userId,
        $action,
        $module,
        $subId,
        $count,
        $persist,
        $allowExceedLimit = false,
        $effectBuffSettings = array(),
        $logCommodityConsume = true,
        $commodityValue = 0
    ) {
        return GameService::handleEntity($userId, EntityService::get()->buildEntityId($action, $module, $subId), $count, $persist, $allowExceedLimit, $effectBuffSettings, $logCommodityConsume,
            $commodityValue);
    }

    /**
     * 升级处理
     *
     * @param int $userId
     * @param ModuleProfileVo $profile
     * @param int $oldLevel 升级前的等级
     * @return void
     */
    public function handleLevelUp($userId, $profile, $oldLevel = 0)
    {

        // 更新任务进度
        MissionService::get()->progress($userId, GameConst::ENTITY_ACTION_USER_LEVEL, GameConst::ENTITY_TYPE_NULL, '', $profile->getLevel());
        // 解锁培育/变色
        //        CultivateService::get()->flushUnlockLand($userId);
        // 等级解锁种子
        SeedService::get()->handleLevelUp($userId, $profile->getLevel());

        // 种子等级排行榜
        SeedService::get()->setFlowerLevel($userId,GameService::get()->getReqTime());

        // 刷新活动
        ActivityService::get()->flush($userId, null, null, $profile->getLevel());
        // 培育任务
        $seedList = SzPersister::get()->getVoList($userId, GameConst::ORM_NAME_PLANT_SEED);
        NewTaskService::get()->setProgress($userId,GameConst::TASK_CULTIVATE,count($seedList->toArray()));

        SeedService::get()->setFlowerLevel($userId,$this->getReqTime(),false);

        // 每日任务
        NewTaskService::get()->initDailyTask($userId,true);


    }

    /**
     * 加速花费
     *
     * @param int $userId
     * @param int $coolDownId
     * @param int $targetTime 目标时间
     * @param int $reqTime 当前时间
     * @param string $logItem default '' 数据采集Item
     * @param int $cliCost 前端回传的加速花费，用来防止前端扣的钱比后端多，造成数据不同步。
     * @throws SzException 20043
     * @return int
     */
    public function handleSpeedUp($userId, $coolDownId, $targetTime, $reqTime, $logItem = '', $cliCost = 0)
    {
        // 验证是否需要加速
        $remaining = $targetTime - $reqTime;
        if ($remaining < 0) {
            $remaining = 0;
        }
        $speedUpSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_SPEEDUP, $coolDownId, true);

        // 计算优币消耗
        $remainingCost = 0;
        foreach ($speedUpSetting['gemCost'] as $minTime => $cost) {
            if ($remaining < $minTime) {
                break;
            }
            $remainingCost = $cost;
        }
        $remainingCost = max($remainingCost, $cliCost);

        GameService::get()->handleEntity($userId, GameConst::ENTITY_ACTION_CONSUME_MONEY, $remainingCost, true);
        CommonFunctionService::get()->reportResourceUseDetails(99999998,$userId,0,GameConst::ENTITY_ACTION_CONSUME_MONEY.':'.$remainingCost.'|','handleSpeedUp');//消耗资源上报

        if ($logItem) {
            // 数据采集：清CD时消耗钻石
            StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USE_GOLD, array(
                'clientData' => array(
                    'item' => $logItem,
                    'value' => abs($remainingCost)
                )
            ));
        }

        return $remainingCost;
    }


    /**
     * 初始化SESSION TOKEN
     *
     * @param int $userId
     * @return int
     */
    public function initSessionToken($userId)
    {
        if (isset($_REQUEST['pf_token']) // "pf_token" to replace the "signed_request" given
            && $_REQUEST['pf_token'] == GameConst::MAGIC_TOKEN // it's magic token
        ) {
            $token = $_REQUEST['pf_token'];
        } else {
            $token = SzUtility::genMd5(uniqid($userId . '_', false));
            SzContextFactory::get()->getStaticCache($userId)->set(GameConst::CACHE_KEY_TOKEN . $userId, $token, SzTime::HOURS24);
        }
        //保存token 给数据采集使用，避免去redis获取
        StatisticsService::get()->saveToken($userId, $token);
        return $token;
    }

    /**
     * 验证SESSION TOKEN
     *
     * @param int $userId
     * @throws SzException 20601
     * @return void
     */
    public function verifySessionToken($userId)
    {
        $token = SzParam::getReqParam('token');
        if (is_null($token)) {
            throw new SzException(20010);

        }
        $appConfig = SzConfig::get()->loadAppConfig('app');
        if ($token == GameConst::MAGIC_TOKEN && $appConfig['UNIVERSAL_TOKEN']) {
            // $this->initSessionToken($userId);
            return; // 如果是万用token，则跳过验证
        }

//        $serverState = $this->getGlobalValue(GameConst::GLOBAL_INFO_TYPE_STATE_SERVER);
//        $writeList = $this->getGlobalValue(GameConst::GLOBAL_INFO_TYPE_WHITE_LIST);
//
//        // 验证服务器是否已经关服
//        if (!is_null($serverState) && $serverState != GameConst::SERVER_OPEN && (!$writeList || !in_array($userId, json_decode($writeList)))) {
//            $msg = $this->getGlobalValue(GameConst::GLOBAL_INFO_TYPE_INFO_STATE_SERVER);
//            throw new SzException(20046, (!$msg) ? '服务器进行常规维护请10分钟后重新尝试登陆' : $msg);
//        }

        // 验证 token 是否合法
        $sessionToken = SzContextFactory::get()->getStaticCache($userId)->get(GameConst::CACHE_KEY_TOKEN . $userId);
        if (false == $sessionToken) {
            throw new SzException(20050);
        }

        if ($token != $sessionToken) {
            throw new SzException(20010);
        }

        //保存token 给数据采集使用，避免去redis获取
        StatisticsService::get()->saveToken($userId, $token);
    }

    /**
     * 验证MAGIC TOKEN
     *
     * @return int
     */
    public function verifyMagicToken()
    {
        if (isset($_REQUEST['pf_token']) // "pf_token" to replace the "signed_request" given
            && $_REQUEST['pf_token'] == GameConst::MAGIC_TOKEN // it's magic token
        ) {
            return true;
        }
        return false;
    }

    /**
     * Create empty voList via Orm
     *
     * @param string $orm
     * @return SzAbstractVoList
     */
    public function createEmptyVoListViaOrm($orm)
    {
        $model = SzPersister::get()->getModel($orm);
        $voListName = $model->getVoListClassName();

        return new $voListName(array());
    }

    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    //-* Extra Drop
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    /**
     * 计算系统额外掉落
     *
     * @param int $dropType
     * @param int $level
     * @return array
     */
    public function calcGlobalDrop($dropType, $level)
    {
        $globalDropSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_GLOBAL_DROP, null, true);

        // 类型过滤，等级过滤，生成掉落列表
        $dropList = array();
        foreach ($globalDropSettings as $globalDropSetting) {
            if ($globalDropSetting['dropType'] == $dropType && $globalDropSetting['level'] <= $level) {
                $dropList[] = $globalDropSetting;
            }
        }

        // 掉落列表为空
        $dropCount = count($dropList);
        if ($dropCount == 0) {
            return array();
        }

        // 从掉落列表中随机取一个掉落条目
        $randDropSetting = $dropList[rand(0, $dropCount - 1)];
        $drop = array();
        for ($i = 0; $i < $randDropSetting['time']; $i++) {
            $dropItemEntity = GameService::getRandomElementByProbability($randDropSetting['dropItem']);
            if ($dropItemEntity != GameConst::PADDING_ENTITY_ID && $dropItemEntity) {
                if (isset($dropItemEntity) && in_array($dropItemEntity, $drop)) {
                    $drop[$dropItemEntity] += 1;
                } else {
                    $drop[$dropItemEntity] = 1;
                }
            }
        }
        return $drop;
    }

    /**
     * Get Random Element From Array. And if probability is 0, then return this bonus id directly without random logic. <br/>
     * Probability logic: The percentage probability means is determined by the total sum value.
     *
     * <pre>
     * e.g the total sum value is 10000
     * bonusId => probability
     * 10      => 500 5%
     * 11      => 500 5%
     * 12      => 500 5%
     * 13      => 400 4%
     * 14      => 50 0.5%
     * 15      => 50 0.5%
     * 16      => 1500 15%
     * 17      => 1500 15%
     * 18      => 5000 50%
     * </pre>
     *
     * @param array $probabilityList
     * <pre>
     * array(
     *      bonusId => probability
     * );
     * </pre>
     * @param boolean $filterZeroWeight default false
     * @return int $bonusId
     */
    public static function getRandomElementByProbability($probabilityList, $filterZeroWeight = true)
    {
        if ($filterZeroWeight) {
            foreach ($probabilityList as $id => $probability) {
                if ($probability != 0) {
                    continue;
                }
                unset($probabilityList[$id]);
            }
        }

        return SzUtility::getRandomElementByProbability($probabilityList);
    }

    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    //-* UTILITY
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    /**
     * 获得服务器时间
     *
     * @return int $this->sysTime
     */
    public function getSysTime()
    {
        $time = SzSystem::getSysTime();
        if (SzConfig::get()->loadAppConfig('app', 'GM_OPEN')) {
            $userId = intval(SzParam::getReqParam('userId'));
            $redisDb = SzDbFactory::get()->getDb(null, SzAbstractDb::DB_TYPE_REDIS);
            $diff = $redisDb->hGet(GameConst::CACHE_KEY_DIFF_TIME, $userId);
            if (!$diff) {
                $diff = 0;
            }
            $time += $diff;
        }
        return $time;
    }

    /**
     * 保存/获取 接口调用时间
     *
     * @param int $timestamp default null
     * @return int $this->reqTime
     */
    public function getReqTime($timestamp = null)
    {
        $time = SzSystem::getReqTime($timestamp);
        if (SzConfig::get()->loadAppConfig('app', 'GM_OPEN')) {
            $userId = intval(SzParam::getReqParam('userId'));
            $redisDb = SzDbFactory::get()->getDb(null, SzAbstractDb::DB_TYPE_REDIS);
            $diff = $redisDb->hGet(GameConst::CACHE_KEY_DIFF_TIME, $userId);
            if (!$diff) {
                $diff = 0;
            }
            $time += $diff;
        }
        return $time;
    }

    /**
     * 获取今天的 timestamp
     *
     * @param int $timestamp
     * @return int
     */
    public function getTodayTimestamp($timestamp = null)
    {
        return SzTime::getTimestamp(SzTime::getDate($timestamp ? $timestamp : $this->getReqTime()));
    }

    public function diffDate($time1, $time2)
    {
        $date1 = date("Y-m-d", $time1);
        $date2 = date("Y-m-d", $time2);
        $time1 = strtotime($date1);
        $time2 = strtotime($date2);

        return abs(round(($time1 - $time2) / 3600 / 24));
    }

    /**
     * 保存/获取 语言版本
     *
     * @param int $userId
     * @return int $this->lang
     */
    public function getLang($userId)
    {
        // 如果已经存在语言类型，则不需要读取
        if ($this->lang) {
            return $this->lang;
        }

        /**
         * @var ModuleProfileInfoVoList $profileInfoList
         * @var ModuleProfileInfoVo $langInfo
         */
        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        $langInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_USER_LANG, false);

        if (!$langInfo) {
            $this->lang = SzConfig::get()->loadAppConfig('app', 'LANG');
        } else {
            $this->lang = $this->parseLang($langInfo->getRawInfo());
        }

        return $this->lang;
    }

    /**
     * 解析/匹配 语言版本
     *
     * @param int $needParseLang
     * @return string $lang
     */
    public function parseLang($needParseLang)
    {
        $appConfig = SzConfig::get()->loadAppConfig('app');
        $platform = $appConfig['PLATFORM'];
        if ($platform == 'sina') {
            $lang = $appConfig['LANG'];
        } else {
            // zh_TW, zh_CN
            list($langType) = explode('_', $needParseLang);
            $langMap = json_decode(GameConst::DEFAULT_LANG_MAP, true);
            if (SzUtility::checkArrayKey($langType, $langMap)) {
                if (in_array($needParseLang, $langMap[$langType])) {
                    $lang = $needParseLang; // 语言类型完全匹配
                } else {
                    $lang = array_shift($langMap[$langType]); // 语言类型部分匹配
                }
            } else {
                $lang = $appConfig['LANG'];
            }
        }

        return $lang;
    }

    /**
     * 验证系统功能是否开启
     *
     * @param int $userId 玩家ID
     * @param int $sysId 功能ID
     * @param boolean $noError 禁止报错
     * @throws SzException 20000
     * @return boolean
     */
    public function verifySysUnlock($userId, $sysId, $noError = false)
    {
        $sysConfig = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_SYSTEM_UNLOCK, $sysId, true);

        if ($sysConfig['level'] > UserService::get()->getUserLevel($userId)) {
            if (!$noError) {
                throw new SzException(20000, $userId);
            } else {
                return false;
            }
        }

        return true;
    }

    /**
     * 更新玩家新手引导引导序号和步骤序号
     *
     * @param $userId int
     * @param $groupId int
     * @param $step int
     * @throws SzException
     * @return boolean
     */
    public function updateGuide($userId, $groupId, $step)
    {
        $guideGroupStep = $groupId . '_' . $step;
        $guideSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_GUIDE, null, true);

        // 获取当前已经完成的引导
        $guideInfo = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_GUIDE_INFO);

        // 判断新手引导是否已经完成
        if (in_array($guideGroupStep, $guideInfo)) {
            return true;
        }

        // 检查新手引导是否存在
        $isGuideExist = false;
        $indexId = 0;
        foreach ($guideSettings as $_indexId => $guideSetting) {
            if ($guideSetting['guideGroup'] == $groupId && $guideSetting['step'] == $step) {
                $isGuideExist = true;
                $indexId = $_indexId;
                break;
            }
        }

        if (!$isGuideExist) {
            throw new SzException(20067, array($groupId, $step));
        }

        // 更新新手引导
        $guideInfo[] = $guideGroupStep;
        UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_GUIDE_INFO, $guideInfo);

        // 数据采集:
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_TUTORIAL_STEP, array(
            'clientData' => array(
                'tid' => '',
                'item' => $indexId
            )
        ));

        return true;
    }

    /**
     * 更新 global 数据
     *
     * @param $key
     * @param $value
     * @return void
     */
    public function updateGlobalValue($key, $value)
    {
        $global = SzPersister::get()->getVo(null, GameConst::ORM_NAME_GLOBAL, $key);
        if (!$global) {
            $global = new GlobalVo($key, $value, true);
        }

        $global->setValue($value);
        SzPersister::get()->setVo($global);
    }

    /**
     * 根据传入的 globalKey，返回 globalValue
     *
     * @param string $key
     * @return mixed
     */
    public function getGlobalValue($key)
    {
        /** @var GlobalVo $global */
        $global = SzPersister::get()->getVo(null, GameConst::ORM_NAME_GLOBAL, $key);
        if (!$global) {
            return null;
        }
        return $global->getValue();
    }

    /**
     * 加入黑名单（封号）
     *
     * @param int $userId
     * @param int $lockTime 封号到哪个时间（时间戳）
     * @param string $reason 封号原因
     * @return void
     * @throws SzException
     */
    public function updateBlacklist($userId, $lockTime, $reason = '')
    {
        $blacklist = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_BLACKLIST);
        if (!$blacklist) {
            $blacklist = new BlacklistVo($userId, $lockTime, $reason, true);
        }

        // 更新黑名单
        $blacklist->setLockTime($lockTime);
        $blacklist->setReason($reason);
        SzPersister::get()->setVo($blacklist);
    }

    /**
     * 获取玩家封号时长
     *
     * @param $userId
     * @return int  -1：未封号
     * @throws SzException
     */
    public function getLockTime($userId)
    {
        $blacklist = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_BLACKLIST);
        if (!$blacklist) {
            // 查询封号，如果未查到，先创建一个空的，以防止下一次查询会直接访问数据库。
            $blacklist = new BlacklistVo($userId, -1, '', true);
            SzPersister::get()->setVo($blacklist);
        }

        return $blacklist->getLockTime();
    }

    /**
     * 根据语言类型，查找语言表
     *
     * @param string $type
     * @param string $id
     * @return array
     */
    public function loadLanguageConfig($type, $id)
    {
        switch ($type) {
            case GameConst::LANGUAGE_CN:
                return SzConfig::get()->loadAppConfig(GameConst::CONFIG_CN, $id, true);
                break;
            case GameConst::LANGUAGE_EN:
                return SzConfig::get()->loadAppConfig(GameConst::CONFIG_EN, $id, true);
                break;
            default:
                return SzConfig::get()->loadAppConfig("$type/$type", $id, true);
                break;
        }
    }

    /**
     * 获取语言表的信息
     *
     * @param int $userId
     * @param string $id
     * @return array;
     */
    public function getLanguageConfig($userId, $id)
    {
        return $this->loadLanguageConfig(UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_USER_LANG), $id);
    }


    /**
     * 获取语言表组合的信息
     * @param $userId
     * @param $id string 语言表ID
     * @param $paramArr array 参数数组
     * @return string
     */
    public function getLanguageMsg($userId, $id, $paramArr = array())
    {
        $languageSetting = $this->getLanguageConfig($userId, $id);
        if (!$languageSetting['value']) {
            return '';
        };

        // 替换 paramArr 的字符到配置中
        $result = preg_replace_callback('/{([^{}]+)}/', function ($matches) use ($paramArr) {
            return $paramArr[$matches[1]];
        }, $languageSetting);

        return $result['value'];
    }

    /**
     * GM 工具，关服
     *
     * @param $params
     * @throws SzException
     * @return array
     */
    public function closeServer($params)
    {
        $canClose = SzConfig::get()->loadAppConfig('app', 'STOP_SERVER');
        if (!$canClose) {
            throw new SzException(20072);
        }

        $this->updateGlobalValue(GameConst::GLOBAL_INFO_TYPE_STATE_SERVER, GameConst::SERVER_CLOSE);
        $this->updateGlobalValue(GameConst::GLOBAL_INFO_TYPE_INFO_STATE_SERVER, $params['msg']);

        return array(
            'server_state' => 'close',
            'massage' => $params['msg']
        );
    }

    /**
     * GM 工具，开服
     *
     * @throws SzException
     * @return array
     */
    public function openServer()
    {
        $canClose = SzConfig::get()->loadAppConfig('app', 'STOP_SERVER');
        if (!$canClose) {
            throw new SzException(20072);
        }

        $this->updateGlobalValue(GameConst::GLOBAL_INFO_TYPE_STATE_SERVER, GameConst::SERVER_OPEN);

        return array(
            'server_state' => 'open',
            'massage' => 'no message'
        );
    }

    /**
     * GM 工具，获取服务器状态
     *
     * @throws SzException
     * @return array
     */
    public function serverStatus()
    {
        $serverState = $this->getGlobalValue(GameConst::GLOBAL_INFO_TYPE_STATE_SERVER);
        $message = $this->getGlobalValue(GameConst::GLOBAL_INFO_TYPE_INFO_STATE_SERVER);

        return array(
            'server_state' => $serverState ? 'open' : 'close',
            'massage' => $message
        );
    }

    /**
     * 查询白名单
     *
     * @return array
     */
    public function queryWriteList()
    {
        $whiteList = $this->getGlobalValue(GameConst::GLOBAL_INFO_TYPE_WHITE_LIST);
        $whiteList = json_decode($whiteList, true);

        return array(
            'white_list' => json_encode($whiteList)
        );
    }

    /**
     * 添加白名单
     *
     * @param $params
     * @return array
     */
    public function addWriteList($params)
    {
        $whiteList = $this->getGlobalValue(GameConst::GLOBAL_INFO_TYPE_WHITE_LIST);
        $whiteList = json_decode($whiteList, true);
        $whiteList = (!$whiteList) ? array() : $whiteList;

        // 加入白名单
        foreach ($params['white_list'] as $id) {
            if (!is_numeric($id)) {
                continue;
            }
            if (!in_array($id, $whiteList)) {
                $whiteList[] = $id;
            }
        }

        // 更新白名单
        $this->updateGlobalValue(GameConst::GLOBAL_INFO_TYPE_WHITE_LIST, json_encode($whiteList));

        return array(
            'white_list' => json_encode($whiteList)
        );
    }

    /**
     * 删除白名单
     *
     * @param $params
     * @return array
     */
    public function deleteWriteList($params)
    {
        $whiteList = $this->getGlobalValue(GameConst::GLOBAL_INFO_TYPE_WHITE_LIST);
        $whiteList = json_decode($whiteList, true);

        // 删除白名单
        foreach ($params['white_list'] as $deleteId) {
            if (!in_array($deleteId, $whiteList)) {
                continue;
            }
            array_splice($whiteList, array_search($deleteId, $whiteList), 1);
        }

        // 更新白名单
        $this->updateGlobalValue(GameConst::GLOBAL_INFO_TYPE_WHITE_LIST, json_encode($whiteList));

        return array(
            'white_list' => json_encode($whiteList)
        );
    }

    /**
     * 删除白名单
     *
     * @return array
     */
    public function clearWriteList()
    {
        // 清空白名单
        $this->updateGlobalValue(GameConst::GLOBAL_INFO_TYPE_WHITE_LIST, GameConst::DEFAULT_JSON_STRING);

        return array(
            'white_list' => GameConst::DEFAULT_JSON_STRING
        );
    }

    /**
     * 获取多玩家可互动信息列表
     *
     * @param $userIds
     * @param $userId
     * @return array
     */
    public function getInteraction($userIds, $userId)
    {
        $info = [];
        foreach ($userIds as $friendId) {
            if ($friendId <= 0) {
                continue;
            }
            $info[$friendId] = $this->getInteractionInfo($friendId, $userId);
        }
        return $info;
    }

    /**
     * 获取单个玩家
     *
     * @param $friendId
     * @param $userId
     * @return array [1:浇水,2:互助火车,3:party活动是否参与人数不足,4:EX...]
     */
    private function getInteractionInfo($friendId, $userId)
    {
        $info = [];
        $totalCount = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, 'umberOfMutualaid', true);
        //获取当天交互次数
        $friendCount = FriendService::get()->getFriendHandleInfo($userId, $friendId,$totalCount);
        if ($friendCount < $totalCount['value']) {
            if (PlantService::get()->isCanStealFlower($userId, $friendId)) {
                $info[] = 4;
            }
        }
        return $info;
    }

    /**
     * 检查游戏版本号
     *
     * @param $channel
     * @param $version
     * @return bool
     * @throws SzException
     */
    public function checkVersion($channel, $version)
    {
        // 使用"."进行切割获取前2位是否相同就行
        $temp1 = explode('.', $version);

        // 判断版本参数的格式是否正确
        if (count($temp1) < 2) {
            throw new SzException(20071, 'Version parameter error');
        }

        // 使用"-"进行连接确保是一个字符串
        $inputVer = $temp1[0] . '-' . $temp1[1];

        // 配置文件路径设置
        $content = file_get_contents(SzSystem::$APP_ROOT . '/../../../vrelease/versionInfoConfig.json');
        if (!$content) {
            return true;
        }

        // 获取配置文件内容
        $data = json_decode($content, true);

        // 根绝channel参数获取文件里的版本配置
        $fileVer = '';
        foreach ($data as $value) {
            if ($value['Channel'] != $channel) {
                continue;
            }

            // 使用"."进行切割获取前2位是否相同就行
            $temp2 = explode('.', $value['ResourceVersion']);
            if (count($temp2) < 2) {
                continue;
            }

            $fileVer = $temp2[0] . '-' . $temp2[1];
            break;
        }

        // 如果没有获取到文件中的版本号表示Channel参数错误
        if (empty($fileVer)) {
            throw new SzException(20074);
        }

        // 如果版本不同抛出20075错误客户端进行强制更新
        if ($inputVer != $fileVer) {
            throw new SzException(20075);
        }

        return true;
    }

    /**
     * 是否每日首次登录
     *
     * @param int $userId
     * @param ModuleProfileVo $profile
     * @return boolean
     */
    public function isDailyFirstLogin($userId, $profile = null)
    {
        if (!$profile) {
            $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        }

        return !(SzTime::getDate(GameService::getReqTime()) == SzTime::getDate($profile->getLastLoginTime() ?? 0));
    }

    /**
     * 加锁，遇到锁直接报错。
     * <pre>
     * 1. 加锁后，读取数据必须优先清除在 SystemCache 中的数据，并重新从 mysql，redis 中拉取数据，确保数据最新。
     * 2. 解锁前，必须将 vo，volist 都进行强制持久化。
     * </pre>
     *
     * @param string $key
     * @param int $needWait 是否开启自旋锁
     * @param boolean $strictMode 是否强制解锁，再加锁
     * @param boolean $throwErr 是否报错
     * @throws 10750
     * @return boolean
     */
    // FIXME 开启重试，会造成阻塞，php 性能降低。
    public function lock($key, $needWait = 0, $strictMode = false, $throwErr = true)
    {
        // 强制解锁
        if ($strictMode) {
            $this->unlock($key);
        }

        $lockKey = $this->getLockKey($key);
        $lockInterval = 50; // 默认阻塞毫秒数

        if (!$this->cache->add($lockKey, 1)) {
            // 当 needWait > 0 时候，开启重试，或者尝试5次后，关闭重试
            if ($needWait > 0 && $needWait < 5) {
                // 当 needWait > 1, 或者 needWait 小于 5 的时候，会不断触发重试
                $needWait++;
                usleep($lockInterval * 1000);
                $this->lock($key, $needWait);
            } else {
                // 当 needWait = 0, 或者 needWait 大于 5 的时候，不再触发 try catch 重试。
                if ($throwErr) {
                    throw new SzException(20013, $key);
                } else {
                    return false;
                }
            }
        }
        $this->cache->set($lockKey, 1, 5);

        return true;
    }

    /**
     * 解锁，必须先加锁，才可以解锁
     *
     * @param $key
     * @return void
     */
    public function unlock($key)
    {
        $this->cache->delete($this->getLockKey($key));
    }

    /**
     * 检查是否有锁
     *
     * @param $key
     * @param int $needWait 是否开启自旋锁
     * @param boolean $throwErr 是否报错
     * @throws 10750
     * @return boolean
     */
    public function chklock($key, $needWait = 0, $throwErr = true)
    {
        $lockKey = $this->getLockKey($key);
        $lockInterval = 50; // 默认阻塞毫秒数

        if ($this->cache->get($lockKey)) {
            // 当 needWait > 0 时候，开启重试，或者尝试5次后，关闭重试
            if ($needWait > 0 && $needWait < 5) {
                // 当 needWait > 1, 或者 needWait 小于 5 的时候，会不断触发重试
                $needWait++;
                usleep($lockInterval * 1000);
                $this->chklock($key, $needWait);
            } else {
                // 当 needWait = 0, 或者 needWait 大于 5 的时候，不再触发 try catch 重试。
                if ($throwErr) {
                    throw new SzException(20013, $key);
                } else {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Get Lock key
     *
     * @param string $key
     * @return mixed $value
     */
    private function getLockKey($key)
    {
        return SzAbstractCache::CACHE_LOCK_KEY . $key;
    }

    /**
     * 同步玩家数据
     *
     * @param $userId int 玩家ID
     * @return array
     */
    public function syncUserInfo($userId)
    {
        if (!SzConfig::get()->loadAppConfig('app', 'VERIFY_USER_INFO')) {
            return [];
        }

        return array(
            'level' => UserService::get()->getUserLevel($userId),
            'exp' => UserService::get()->getUserExp($userId),
            'token' => UserService::get()->getToken($userId),
            'money' => PayService::get()->getMoney($userId),
        );
    }

    /**
     * 补充消耗品
     *
     * @param $userId int 玩家ID
     * @param $items array 需要补足的物品数组 {itemId=>$needCount,...}
     * @param $reasonId int 触发原因
     * @param $isEntityId boolean 是否为EntityId 740140010001
     * @throws SzException
     * @return array 返回实际扣除的数量
     * <pre>
     *  array(
     *      $id => $quickBuyCount,
     *      ...
     *  )
     * </pre>
     */
    public function payForQuickBuy($userId, $items, $reasonId, $isEntityId = false)
    {
        /** @var $itemList ModuleItemVoList */
        $itemList = SzPersister::get()->getVoList($userId, ModuleItemConst::ORM_NAME_ITEM);
        $itemSettings = SzConfig::get()->loadAppConfig(ModuleItemConst::CONFIG_ITEM_DEFS, null, true);

        $payForQuickBuyList = array();
        foreach ($items as $id => $needCount) {
            // 读取道具定义id
            if ($isEntityId) {
                list(, , $itemDefId) = EntityService::get()->parseEntityId($id);
            } else {
                $itemDefId = $id;
            }

            // 判断是否有效配置
            if (!SzUtility::checkArrayKey($itemDefId, $itemSettings)) {
                continue;
            }

            // 获取道具的拥有数量
            $ownCount = $itemList->getItemDefCount($itemDefId);
            $needBuyCount = ($ownCount >= $needCount) ? 0 : $needCount - $ownCount;

            if ($needBuyCount > 0) {
                $payForQuickBuyList[$id] = $needBuyCount;
                ItemService::get()->quickBuy($userId, $itemDefId, $needBuyCount, $reasonId, false);
            }
        }

        return $payForQuickBuyList;
    }

    /**
     * 验证是否为同一台设备登陆(首次登陆会创建)
     * @param $userId
     * @param $deviceId
     * @return bool
     */
    public function verifyIsSameDevice($userId, $deviceId)
    {
        if ('dev' == $deviceId || '' == $deviceId) {
            return false;
        }

        $oldDeviceId = SzContextFactory::get()->getStaticCache($userId)->get(GameConst::CACHE_KEY_USER_DEVICE_ID . $userId);
        if ($oldDeviceId === false) {
            return false;
        }

        return $deviceId == $oldDeviceId;
    }

    /**
     * 使用唯一键，进行日志打印，打印日志使用 | grep $uniqueKey 来过滤
     * @param string $uniqueKey
     * @param mixed $value
     * @return void
     */
    public function logger($uniqueKey, $value)
    {
        SzLogger::get()->debug($uniqueKey . '|' . json_encode($value));
    }

    /**
     * 心跳数据定时统计
     * 脚本调用
     * @return string
     */
    public function getUserLive()
    {
        $appConfig = SzConfig::get()->loadAppConfig('app');

        $timestamp = GameService::get()->getReqTime();
        $message = [];
        $message['game_id'] = $appConfig['GAME_ID'];
        $message['area_id'] = 9999;
        $message['uid'] = '';
        $message['time'] = date('Y-m-d H:m:s', $timestamp);
        $message['cash'] = 0;
        $message['coin'] = 0;
        $message['level'] = 0;
        $message['rtime'] = '';
        $message['token'] = '';
        $message['ip'] = SzUtility::getServerHost();
        $message['tid'] = '';
        $message['item'] = '';
        $message['value'] = count(SzCacheFactory::get()->getAppCache()->keys(GameConst::CACHE_KEY_USER_LIVE . ':*'));
        $message['uuid'] = '';
        $message['act'] = 'online';
        $message['value2'] = 0;

        $req['filter'] = 'FT_ONLINE_EN';
        $req['channel'] = 'business';
        $req['time'] = $timestamp;
        $req['message'] = $message;
        $req['sign'] = $this->makeSign($req);
        $req['message'] = json_encode($req['message']);

        $url = ($appConfig['ENV'] == 'DEV') ? GameConst::CONFIG_USER_CENTER_BEATS_DEV : GameConst::CONFIG_USER_CENTER_BEATS_LIVE;

        $res = $this->postHttpRequest($url, json_encode($req));
        return $res;
    }

    /**
     *  生成签名  给数据中心发送在线人数接口使用
     * @param array $params
     * @return string
     */
    private function makeSign($params)
    {
        ksort($params['message']);
        $params['message'] = SzUtility::buildQueryString($params['message'], false);
        ksort($params);
        $sign = SzUtility::buildQueryString($params, false);

        return md5($sign . GameConst::CONFIG_USER_CENTER_SIGN_SECURITY_KEY);
    }

    /**
     * 删除emjoy字符
     * @param $str
     * @return mixed
     */
    function delEmjoy($str)
    {
        return preg_replace_callback('/[\xf0-\xf7].{3}/', function () {
        }, $str);
    }

    /**
     * utf8 to unicode
     * @param $str
     * @return string
     */
    public function utf82unicode($str)
    {
        if (!$str) {
            return $str;
        }
        $str = json_encode($str);
        return $str;
    }

    /**
     * unicode to utf8
     * @param $str
     * @return mixed|string
     */
    public function unicode2utf8($str)
    {
        if (!$str) {
            return $str;
        }
        $decode = json_decode($str);
        if ($decode) {
            return $decode;
        }
        $str = '["' . $str . '"]';
        $decode = json_decode($str);
        if (count($decode) == 1) {
            return $decode[0];
        }
        return $str;
    }

    /**
     * Post a http request.
     *
     * @param string $url
     * @param array|string $params 'key => value', if string given means this query data already formatted
     * @param boolean $needEncode default true
     * @param int $timeout default 10 => 10s
     * @throws SzException 10200
     * @return string $result
     */
    public static function postHttpRequest($url, $params, $needEncode = true, $timeout = 10)
    {
        $result = null;

        //        $queryString = is_array($params) ? SzUtility::buildQueryString($params, $needEncode) : $params;

        // post the query and get result
        if (function_exists('curl_init')) {
            // Use CURL if installed...
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); // default connect timeout 2s
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($params)
            ));
            $result = curl_exec($ch);
            if (false === $result) {
                curl_close($ch);
                throw new SzException(10200, array(curl_errno($ch), curl_error($ch)));
            }
            curl_close($ch);
        } else {
            // Non-CURL based version...
            $context = array(
                'http' => array(
                    'method' => 'POST',
                    'header' => 'Content-Type: application/json; charset=utf-8' . "\r\n" . 'User-Agent: PHP5 Framework ' . "\r\n" . 'Content-length: ' . strlen($params),
                    'content' => $params
                ),
            );
            $contextId = stream_context_create($context);
            $sock = fopen($url, 'r', false, $contextId);
            if ($sock) {
                $result = '';
                while (!feof($sock)) {
                    $result .= fgets($sock, 4096);
                }
                fclose($sock);
            }
        }

        return $result;
    }

    public function parseJscode2SessionRes($res)
    {
        SzLogger::get()->debug($res);
        $res = is_array($res) ? $res : json_decode($res, true);
        if (isset($res['session_key'])) {
            return [true, $res];
        }
        return [false, $res];
    }

    /**
     * 月卡领奖
     * @param $userId
     * @param $reqTime
     * @return bool
     * @throws SzException
     */
    public function monthCardReward($userId,$reqTime) {
        $everyVipReward = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, 'everyVipReward', true);
        $everyVipReward = json_decode($everyVipReward['value'],true);
        if (!$everyVipReward) {
            return false;
        }

        /**
         * @var ModuleProfileInfoVo $monthCardProfileInfo
         * @var ModuleProfileInfoVo $monthCardRewardProfileInfo
         */
        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        $monthCardProfileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_MONTH_CARD, false);
        if (!$monthCardProfileInfo || ((int)$monthCardProfileInfo->getRawInfo() < $reqTime)) {
            return false;
        }

        $monthCardRewardProfileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_MONTH_CARD_REWARD, false);
        if (!$monthCardRewardProfileInfo) {
            $monthCardRewardProfileInfo = new ModuleProfileInfoVo(
                $userId,
                GameConst::USER_INFO_TYPE_MONTH_CARD_REWARD,
                0,
                $reqTime,
                true
            );
        }

        if (date('Ymd',$reqTime) == date('Ymd',(int)$monthCardRewardProfileInfo->getRawInfo())) {
            return false;
        }

        $output = '';
        foreach ($everyVipReward as $entityId => $count) {
            GameService::get()->handleEntity($userId, $entityId, $count, true);
            $output .= $entityId.':'.$count.'|';
        }
        CommonFunctionService::get()->reportResourceUseDetails(99999997,$userId,1,$output,'monthCardReward');//获得资源上报

        $monthCardRewardProfileInfo->setInfo($reqTime);
        if ($monthCardRewardProfileInfo->isInsert() && !SzUtility::checkArrayKey(GameConst::USER_INFO_TYPE_MONTH_CARD_REWARD, $profileInfoList->getList())) {
            $profileInfoList->addElement($monthCardRewardProfileInfo);
            $profileInfoList->setElement(GameConst::USER_INFO_TYPE_MONTH_CARD_REWARD,$monthCardRewardProfileInfo);
        } else {
            $profileInfoList->updateElement(GameConst::USER_INFO_TYPE_MONTH_CARD_REWARD,$monthCardRewardProfileInfo);
        }
        SzPersister::get()->setVoList($profileInfoList);

        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:vipGift|action:get|',
                'item' => 'vipGift_get',
                'value' => 1
            )
        ));

        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_RESOURCE_OUTPUT, array(
            'clientData' => array(
                'tid' => $output,
                'item' => 'vipGift_get',
                'value' => 1
            )
        ));

        return true;
    }

    /**
     * @param $userId
     * @param $reqTime
     * @return bool
     */
    public function collection($userId,$reqTime) {
        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        $collectionStatusProfileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_COLLECTION_STATUS, false);
        if ($collectionStatusProfileInfo) {
            return false;
        }

        $collectionStatusProfileInfo = new ModuleProfileInfoVo(
            $userId,
            GameConst::USER_INFO_TYPE_COLLECTION_STATUS,
            1,
            $reqTime,
            true
        );

        $profileInfoList->addElement($collectionStatusProfileInfo);
        $profileInfoList->setElement(GameConst::USER_INFO_TYPE_COLLECTION_STATUS,$collectionStatusProfileInfo);
        SzPersister::get()->setVoList($profileInfoList);

        return true;
    }

    /**
     * @param $userId
     * @return bool
     * @throws SzException
     */
    public function collectionReward($userId) {
        $collectReward = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, 'CollectReward', true);
        if (!$collectReward) {
            return false;
        }

        $collectReward = json_decode($collectReward['value'],true);

        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        /**
         * @var ModuleProfileInfoVo $collectionStatusProfileInfo
         */
        $collectionStatusProfileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_COLLECTION_STATUS,false);
        $platform = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_USER_PLATFORM);
        if ($platform == 'qq' || $platform == 'dev' || $platform == 'H5WeChat' ||  $platform == 'tt'){
            $collectionStatusProfileInfo = new ModuleProfileInfoVo(
                $userId,
                GameConst::USER_INFO_TYPE_COLLECTION_STATUS,
                2,
                SzSystem::getSysTime(),
                true
            );
            $profileInfoList->addElement($collectionStatusProfileInfo);
            $profileInfoList->setElement(GameConst::USER_INFO_TYPE_COLLECTION_STATUS,$collectionStatusProfileInfo);
            SzPersister::get()->setVoList($profileInfoList);
        }

        if ($platform != 'qq' && $platform != 'dev' && $platform != 'H5WeChat' &&  $platform != 'tt' && (int)$collectionStatusProfileInfo->getRawInfo() == 2) {
            return false;
        }

        $output = '';
        foreach ($collectReward as $entityId=>$count) {
            GameService::get()->handleEntity($userId, $entityId, $count, true);
            $output .= $entityId.':'.$count.'|';
        }
        CommonFunctionService::get()->reportResourceUseDetails(99999995,$userId,1,$output,'collectionReward');//获得资源上报

        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:myApplet|action:add|',
                'item' => 'myApplet_add',
                'value' => 1
            )
        ));

        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_RESOURCE_OUTPUT, array(
            'clientData' => array(
                'tid' => $output,
                'item' => 'myApplet_add',
                'value' => 1
            )
        ));

        $collectionStatusProfileInfo->setInfo(2);
        $profileInfoList->updateElement(GameConst::USER_INFO_TYPE_COLLECTION_STATUS,$collectionStatusProfileInfo);
        SzPersister::get()->setVoList($profileInfoList);
        return true;
    }

}
